#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
天翼云盘PC客户端工具类
包含HTTP请求、加密解密、时间处理、签名生成等工具方法
"""

import os
import json
import time
import hashlib
import hmac
import uuid
import re
import base64
import binascii
import random
from datetime import datetime
from typing import Dict, Optional, Any, Union, BinaryIO, List
from collections import OrderedDict
import threading
import requests
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5, AES
from Crypto.Util.Padding import pad


class PathCacheManager:
    """路径缓存管理器 - 用于缓存路径到文件ID的映射"""
    
    def __init__(self, max_size: int = 10000, enable_cache: bool = True):
        """
        初始化路径缓存管理器
        
        Args:
            max_size: 最大缓存条目数，默认10000
            enable_cache: 是否启用缓存，默认True
        """
        self.max_size = max_size
        self.enable_cache = enable_cache
        self._cache = OrderedDict()  # 使用OrderedDict实现LRU
        self._lock = threading.RLock()  # 线程安全锁
        self._stats = {
            'hits': 0,
            'misses': 0,
            'evictions': 0
        }
    
    def get(self, path: str) -> Optional[Dict[str, Any]]:
        """
        从缓存获取路径对应的文件信息
        
        Args:
            path: 文件路径
            
        Returns:
            Optional[Dict[str, Any]]: 文件信息，如果不存在返回None
        """
        if not self.enable_cache:
            return None
            
        with self._lock:
            if path in self._cache:
                # 移动到末尾（最近访问）
                value = self._cache.pop(path)
                self._cache[path] = value
                self._stats['hits'] += 1
                return value.copy()  # 返回副本避免外部修改
            else:
                self._stats['misses'] += 1
                return None
    
    def put(self, path: str, file_info: Dict[str, Any]) -> None:
        """
        将路径和文件信息放入缓存
        
        Args:
            path: 文件路径
            file_info: 文件信息
        """
        if not self.enable_cache:
            return
            
        with self._lock:
            # 如果已存在，先删除旧的
            if path in self._cache:
                del self._cache[path]
            
            # 检查是否需要淘汰
            while len(self._cache) >= self.max_size:
                # 删除最久未访问的项目（LRU）
                oldest_path, _ = self._cache.popitem(last=False)
                self._stats['evictions'] += 1
            
            # 添加新项目
            self._cache[path] = file_info.copy()
    
    def remove(self, path: str) -> bool:
        """
        从缓存中移除指定路径
        
        Args:
            path: 文件路径
            
        Returns:
            bool: 是否成功移除
        """
        if not self.enable_cache:
            return False
            
        with self._lock:
            if path in self._cache:
                del self._cache[path]
                return True
            return False
    
    def clear(self) -> None:
        """清空缓存"""
        with self._lock:
            self._cache.clear()
            self._stats = {'hits': 0, 'misses': 0, 'evictions': 0}
    
    def invalidate_prefix(self, prefix: str) -> int:
        """
        使指定前缀的缓存失效
        
        Args:
            prefix: 路径前缀
            
        Returns:
            int: 失效的缓存条目数
        """
        if not self.enable_cache:
            return 0
            
        with self._lock:
            keys_to_remove = [key for key in self._cache.keys() if key.startswith(prefix)]
            for key in keys_to_remove:
                del self._cache[key]
            return len(keys_to_remove)
    
    def get_stats(self) -> Dict[str, Any]:
        """
        获取缓存统计信息
        
        Returns:
            Dict[str, Any]: 统计信息
        """
        with self._lock:
            total_requests = self._stats['hits'] + self._stats['misses']
            hit_rate = self._stats['hits'] / total_requests if total_requests > 0 else 0
            
            return {
                'size': len(self._cache),
                'max_size': self.max_size,
                'hits': self._stats['hits'],
                'misses': self._stats['misses'],
                'evictions': self._stats['evictions'],
                'hit_rate': hit_rate,
                'enabled': self.enable_cache
            }
    
    def set_enabled(self, enabled: bool) -> None:
        """
        设置缓存是否启用
        
        Args:
            enabled: 是否启用
        """
        with self._lock:
            self.enable_cache = enabled
            if not enabled:
                self.clear()


class FolderCacheManager:
    """文件夹缓存管理器 - 用于缓存文件夹内容"""
    
    def __init__(self, max_size: int = 1000, enable_cache: bool = True, ttl: int = 300):
        """
        初始化文件夹缓存管理器
        
        Args:
            max_size: 最大缓存条目数，默认1000
            enable_cache: 是否启用缓存，默认True
            ttl: 缓存生存时间（秒），默认300秒（5分钟）
        """
        self.max_size = max_size
        self.enable_cache = enable_cache
        self.ttl = ttl
        self._cache = OrderedDict()  # 存储格式: {folder_id: {'data': files_list, 'timestamp': time}}
        self._lock = threading.RLock()
        self._stats = {
            'hits': 0,
            'misses': 0,
            'evictions': 0,
            'expired': 0
        }
    
    def get(self, folder_id: str) -> Optional[List[Dict[str, Any]]]:
        """
        从缓存获取文件夹内容
        
        Args:
            folder_id: 文件夹ID
            
        Returns:
            Optional[List[Dict[str, Any]]]: 文件列表，如果不存在或过期返回None
        """
        if not self.enable_cache:
            return None
            
        with self._lock:
            if folder_id in self._cache:
                cache_entry = self._cache[folder_id]
                current_time = time.time()
                
                # 检查是否过期
                if current_time - cache_entry['timestamp'] > self.ttl:
                    del self._cache[folder_id]
                    self._stats['expired'] += 1
                    self._stats['misses'] += 1
                    return None
                
                # 移动到末尾（最近访问）
                self._cache.move_to_end(folder_id)
                self._stats['hits'] += 1
                return [item.copy() for item in cache_entry['data']]  # 返回深拷贝
            else:
                self._stats['misses'] += 1
                return None
    
    def put(self, folder_id: str, files_list: List[Dict[str, Any]]) -> None:
        """
        将文件夹内容放入缓存
        
        Args:
            folder_id: 文件夹ID
            files_list: 文件列表
        """
        if not self.enable_cache:
            return
            
        with self._lock:
            # 如果已存在，先删除旧的
            if folder_id in self._cache:
                del self._cache[folder_id]
            
            # 检查是否需要淘汰
            while len(self._cache) >= self.max_size:
                # 删除最久未访问的项目（LRU）
                oldest_id, _ = self._cache.popitem(last=False)
                self._stats['evictions'] += 1
            
            # 添加新项目
            self._cache[folder_id] = {
                'data': [item.copy() for item in files_list],  # 存储深拷贝
                'timestamp': time.time()
            }
    
    def remove(self, folder_id: str) -> bool:
        """
        从缓存中移除指定文件夹
        
        Args:
            folder_id: 文件夹ID
            
        Returns:
            bool: 是否成功移除
        """
        if not self.enable_cache:
            return False
            
        with self._lock:
            if folder_id in self._cache:
                del self._cache[folder_id]
                return True
            return False
    
    def clear(self) -> None:
        """清空缓存"""
        with self._lock:
            self._cache.clear()
            self._stats = {'hits': 0, 'misses': 0, 'evictions': 0, 'expired': 0}
    
    def cleanup_expired(self) -> int:
        """
        清理过期的缓存条目
        
        Returns:
            int: 清理的条目数
        """
        if not self.enable_cache:
            return 0
            
        with self._lock:
            current_time = time.time()
            expired_keys = []
            
            for folder_id, cache_entry in self._cache.items():
                if current_time - cache_entry['timestamp'] > self.ttl:
                    expired_keys.append(folder_id)
            
            for key in expired_keys:
                del self._cache[key]
                self._stats['expired'] += 1
            
            return len(expired_keys)
    
    def get_stats(self) -> Dict[str, Any]:
        """
        获取缓存统计信息
        
        Returns:
            Dict[str, Any]: 统计信息
        """
        with self._lock:
            total_requests = self._stats['hits'] + self._stats['misses']
            hit_rate = self._stats['hits'] / total_requests if total_requests > 0 else 0
            
            return {
                'size': len(self._cache),
                'max_size': self.max_size,
                'ttl': self.ttl,
                'hits': self._stats['hits'],
                'misses': self._stats['misses'],
                'evictions': self._stats['evictions'],
                'expired': self._stats['expired'],
                'hit_rate': hit_rate,
                'enabled': self.enable_cache
            }
    
    def set_enabled(self, enabled: bool) -> None:
        """
        设置缓存是否启用
        
        Args:
            enabled: 是否启用
        """
        with self._lock:
            self.enable_cache = enabled
            if not enabled:
                self.clear()


class HttpUtils:
    """HTTP工具类"""
    pass


class CryptoUtils:
    """加密解密工具类"""
    
    @staticmethod
    def rsa_encrypt(public_key: str, data: str) -> Optional[str]:
        """RSA加密"""
        try:
            # 处理公钥格式
            if not public_key.startswith('-----BEGIN'):
                public_key = f"-----BEGIN PUBLIC KEY-----\n{public_key}\n-----END PUBLIC KEY-----"
            
            # 创建RSA密钥对象
            rsa_key = RSA.import_key(public_key)
            cipher = PKCS1_v1_5.new(rsa_key)
            
            # 加密数据
            encrypted_data = cipher.encrypt(data.encode('utf-8'))
            # 转换为大写十六进制字符串
            return binascii.hexlify(encrypted_data).decode().upper()
        except Exception as e:
            print(f"RSA加密失败: {e}")
            return None
    
    @staticmethod
    def md5_hash(data: Union[str, bytes]) -> str:
        """计算MD5哈希值"""
        if isinstance(data, str):
            data = data.encode('utf-8')
        return hashlib.md5(data).hexdigest()

    @staticmethod
    def get_file_size(file_data: Union[str, bytes, BinaryIO]) -> int:
        """获取文件大小
        
        Args:
            file_data: 文件数据
        
        Returns:
            int: 文件大小（字节）
        """
        if isinstance(file_data, str):
            # 文件路径
            return os.path.getsize(file_data)
        elif isinstance(file_data, bytes):
            # 字节数据
            return len(file_data)
        elif hasattr(file_data, 'seek') and hasattr(file_data, 'tell'):
            # 文件对象
            current_pos = file_data.tell()
            file_data.seek(0, 2)  # 移动到文件末尾
            size = file_data.tell()
            file_data.seek(current_pos)  # 恢复原位置
            return size
        else:
            raise ValueError("不支持的文件数据类型")

    @staticmethod
    def calculate_file_md5(file_data: Union[str, bytes, BinaryIO]) -> str:
        """计算文件MD5
        
        Args:
            file_data: 文件数据
        
        Returns:
            str: MD5值
        """
        import hashlib
        
        md5_hash = hashlib.md5()
        
        if isinstance(file_data, str):
            # 文件路径
            with open(file_data, 'rb') as f:
                for chunk in iter(lambda: f.read(4096), b""):
                    md5_hash.update(chunk)
        elif isinstance(file_data, bytes):
            # 字节数据
            md5_hash.update(file_data)
        elif hasattr(file_data, 'read'):
            # 文件对象
            current_pos = file_data.tell()
            file_data.seek(0)
            for chunk in iter(lambda: file_data.read(4096), b""):
                md5_hash.update(chunk)
            file_data.seek(current_pos)  # 恢复原位置
        else:
            raise ValueError("不支持的文件数据类型")
        
        return md5_hash.hexdigest()

    @staticmethod
    def calculate_slice_size(file_size: int) -> int:
        """计算分片大小
        
        Args:
            file_size: 文件大小（字节）
        
        Returns:
            int: 分片大小（字节）
        """
        if file_size <= 1024 * 1024 * 10:  # 10MB以下
            return 1024 * 1024  # 1MB
        elif file_size <= 1024 * 1024 * 100:  # 100MB以下
            return 1024 * 1024 * 5  # 5MB
        else:
            return 1024 * 1024 * 10  # 10MB


class TimeUtils:
    """时间工具类"""
    
    @staticmethod
    def get_timestamp() -> int:
        """获取当前时间戳（毫秒）"""
        return int(time.time() * 1000)
    
    @staticmethod
    def get_http_date_str() -> str:
        """获取HTTP规范时间"""
        return time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime())
    
    @staticmethod
    def format_datetime(timestamp: Union[int, float, str], 
                       format_str: str = '%Y-%m-%d %H:%M:%S') -> str:
        """格式化时间戳为字符串"""
        try:
            if isinstance(timestamp, str):
                timestamp = float(timestamp)
            if timestamp > 1e10:  # 毫秒时间戳
                timestamp = timestamp / 1000
            return datetime.fromtimestamp(timestamp).strftime(format_str)
        except (ValueError, OSError):
            return str(timestamp)
    
    @staticmethod
    def parse_datetime(date_str: str, format_str: str = '%Y-%m-%d %H:%M:%S') -> Optional[datetime]:
        """解析时间字符串为datetime对象"""
        try:
            return datetime.strptime(date_str, format_str)
        except ValueError:
            return None


class StorageUtils:
    """存储工具类"""
    
    @staticmethod
    def save_json(data: Dict[str, Any], file_path: str) -> bool:
        """保存JSON数据到文件"""
        try:
            # 确保目录存在
            os.makedirs(os.path.dirname(file_path), exist_ok=True)
            with open(file_path, 'w', encoding='utf-8') as f:
                json.dump(data, f, ensure_ascii=False, indent=2)
            return True
        except Exception as e:
            print(f"保存JSON文件失败: {e}")
            return False
    
    @staticmethod
    def load_json(file_path: str) -> Optional[Dict[str, Any]]:
        """从文件加载JSON数据"""
        try:
            if os.path.exists(file_path):
                with open(file_path, 'r', encoding='utf-8') as f:
                    return json.load(f)
        except Exception as e:
            print(f"加载JSON文件失败: {e}")
        return None
    
    @staticmethod
    def ensure_dir(dir_path: str) -> bool:
        """确保目录存在"""
        try:
            os.makedirs(dir_path, exist_ok=True)
            return True
        except Exception as e:
            print(f"创建目录失败: {e}")
            return False


class SignatureUtils:
    """签名工具类"""
    
    @staticmethod
    def hmac_sha1(key: str, data: str) -> str:
        """HMAC-SHA1签名"""
        try:
            signature = hmac.new(
                key.encode('utf-8'),
                data.encode('utf-8'),
                hashlib.sha1
            ).hexdigest()
            return signature
        except Exception as e:
            print(f"HMAC-SHA1签名失败: {e}")
            return ""


class TianyiUtils:
    """天翼云盘专用工具类"""
    
    @staticmethod
    def generate_identity(username: str, password: str) -> str:
        """生成用户身份标识"""
        return hashlib.md5((username + password).encode()).hexdigest()


class TokenCacheUtils:
    """Token缓存工具类"""
    
    @staticmethod
    def save_token(username: str, token_info: Dict[str, Any], cache_dir: str) -> bool:
        """保存token到本地缓存"""
        try:
            cache_file = os.path.join(cache_dir, f"tianyi_token_{CryptoUtils.md5_hash(username)}.json")
            cache_data = {
                'username': username,
                'token_info': token_info,
                'save_time': TimeUtils.get_timestamp(),
                'identity': TianyiUtils.generate_identity(username, token_info.get('sessionSecret', ''))
            }
            return StorageUtils.save_json(cache_data, cache_file)
        except Exception as e:
            print(f"保存token缓存失败: {e}")
            return False
    
    @staticmethod
    def load_token(username: str, cache_dir: str) -> Optional[Dict[str, Any]]:
        """从本地缓存加载token"""
        try:
            cache_file = os.path.join(cache_dir, f"tianyi_token_{CryptoUtils.md5_hash(username)}.json")
            cache_data = StorageUtils.load_json(cache_file)
            
            if cache_data and cache_data.get('username') == username:
                # 检查token是否过期（24小时）
                save_time = cache_data.get('save_time', 0)
                current_time = TimeUtils.get_timestamp()
                if current_time - save_time < 24 * 60 * 60 * 1000:  # 24小时内有效
                    return cache_data.get('token_info')
            
            return None
        except Exception as e:
            print(f"加载token缓存失败: {e}")
            return None
    
    @staticmethod
    def clear_token(username: str, cache_dir: str) -> bool:
        """清除token缓存"""
        try:
            cache_file = os.path.join(cache_dir, f"tianyi_token_{CryptoUtils.md5_hash(username)}.json")
            if os.path.exists(cache_file):
                os.remove(cache_file)
            return True
        except Exception as e:
            print(f"清除token缓存失败: {e}")
            return False